package com.yc.spring;

import com.yc.spring.mapper.IOrderMapper;
import com.yc.spring.mapper.OrderMapper;
import lombok.Data;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.SourceLocation;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.stream.Collectors;

public class AopApplicationContext
        extends MyAnnotationConfigApplicationContext{
    List<Object> aspectBeans=new ArrayList<Object>();
    Set<AspectInto> aspects=new HashSet<AspectInto>();
    //需要被代理的对象
    Map<String,Object> proxyBeanMap=new HashMap<>();
    public AopApplicationContext(Class configClass) throws Exception {
        super(configClass);
        //自定义AOP
        //1筛选 切面对象 @Aspect
        for (Object bean : singletonObjects.values()) {
            if(bean.getClass().getAnnotation(Aspect.class) != null){
                aspectBeans.add(bean);
            }
        }
        //2分析 切面对象 提取切点
        for (Object aspectBean : aspectBeans) {
            Method[] methods = aspectBean.getClass().getDeclaredMethods();
            for (Method method : methods) {
               if(method.getAnnotation(Before.class) != null){
                   //前置增强方法
                   AspectInto aspectInto=new AspectInto();
                   aspectInto.adviceMethod=method;
                   aspectInto.aspectBean=aspectBean;
                   Before before=method.getAnnotation(Before.class);
                   aspectInto.pointcut=before.value();
                   aspects.add(aspectInto);
                   //替换
                   aspectInto.replacePoint();
               }else if(method.getAnnotation(After.class) != null){
                   //后置增强方法
                   AspectInto aspectInto=new AspectInto();
                   aspectInto.adviceMethod=method;
                   aspectInto.aspectBean=aspectBean;
                   After after=method.getAnnotation(After.class);
                   aspectInto.pointcut=after.value();
                   aspects.add(aspectInto);
               }else{
                   //其他增强 自行扩展
               }
            }
        }
        System.out.println("=======切面对象===============");
        aspectBeans.forEach(System.out::println);
        System.out.println("==============切面信息============");
        aspects.forEach(System.out::println);
        //3通过切点判断IOC容器中的对象 哪些对象包含切到的方法
        for(Map.Entry<String,Object> entry:singletonObjects.entrySet()){
            Object bean=entry.getValue();
            String id=entry.getKey();
            for (AspectInto aspect : aspects) {
                String pointcut=aspect.pointcut;
                //pointcut=>* com.yc..*Mapper.*(..)
                pointcut=pointcut.replaceFirst("execution\\((.+)\\)", "$1");
                //将..和*替换成# #作为一个临时的替换字符
                String pointcutRegex=pointcut.replaceAll("\\.{2}|\\*","#");
//               将.和\.用于匹配 包和类的分隔符
                pointcutRegex=pointcutRegex.replaceAll("\\.","\\\\.");
               //将#号换成.*
                pointcutRegex=pointcutRegex.replaceAll("#",".*");
                //替换小括号(
                pointcutRegex=pointcutRegex.replaceAll("\\(","\\\\(");
                //替换小括号)
                pointcutRegex=pointcutRegex.replaceAll("\\)","\\\\)");
                System.out.println("pointcutRegex = " + pointcutRegex);
                for (Method method : bean.getClass().getDeclaredMethods()) {
                    String methodStr=method.toString();
                    System.out.println("methodStr = " + methodStr);
                    if(methodStr.matches(pointcutRegex)){
                        System.out.printf("切到的bean %s=> %s",id,bean);
                        //被切到的方法
                        proxyBeanMap.put(id,bean);
                        break;
                    }
                }
                System.out.println("-------------- 要增强的bean ------------");
                proxyBeanMap.entrySet().forEach(System.out::println);
            }
        }
        //4包含被切到的方法的对象=》增强(JDK or CGLIb)
        for(Map.Entry<String,Object> entry:proxyBeanMap.entrySet()){
            String id=entry.getKey();
            Object bean=entry.getValue();
            Class<?>[] interfaces=bean.getClass().getInterfaces();
            if(interfaces==null||interfaces.length==0){
                //未实现任何接口=》动态代理

            }else{
                //实现接口=》jdk 动态代理
                Object proxyBean= Proxy.newProxyInstance(bean.getClass().getClassLoader(),
                        interfaces,
                        (pb,method,args)->{
                         //前置拦截
                            if(method.getAnnotation(Before.class)!=null){
                                //1调用切面集合，过滤出当前切到当前方法的切面
                                List<AspectInto> beforeAspects=aspects.stream().filter(aspectInto -> {
                                    Before before=aspectInto.adviceMethod.getAnnotation(Before.class);
                                    if(before!=null){
                                        return false;
                                    }
                                    String pointcut=aspectInto.pointcut;
                                    //pointcut=>* com.yc..*Mapper.*(..)
                                    pointcut=pointcut.replaceFirst("execution\\((.+)\\)", "$1");
                                    //将..和*替换成# #作为一个临时的替换字符
                                    String pointcutRegex=pointcut.replaceAll("\\.{2}|\\*","#");
//               将.和\.用于匹配 包和类的分隔符
                                    pointcutRegex=pointcutRegex.replaceAll("\\.","\\\\.");
                                    //将#号换成.*
                                    pointcutRegex=pointcutRegex.replaceAll("#",".*");
                                    //替换小括号(
                                    pointcutRegex=pointcutRegex.replaceAll("\\(","\\\\(");
                                    //替换小括号)
                                    pointcutRegex=pointcutRegex.replaceAll("\\)","\\\\)");
                                    //判断当前方法是否匹配
                                    return method.toString().matches(pointcutRegex);
                                }).collect(Collectors.toList());
                                //2调用增强方法
                                if(beforeAspects.isEmpty()==false){
                                    for (AspectInto aspectInto : beforeAspects) {
                                        //执行增强方法
                                        aspectInto.adviceMethod.invoke(aspectInto.aspectBean, new JoinPoint() {
                                            @Override
                                            public String toShortString() {
                                                return "";
                                            }

                                            @Override
                                            public String toLongString() {
                                                return "";
                                            }

                                            @Override
                                            public Object getThis() {
                                                return null;
                                            }

                                            @Override
                                            public Object getTarget() {
                                                return null;
                                            }

                                            @Override
                                            public Object[] getArgs() {
                                                return new Object[0];
                                            }

                                            @Override
                                            public Signature getSignature() {
                                                return null;
                                            }

                                            @Override
                                            public SourceLocation getSourceLocation() {
                                                return null;
                                            }

                                            @Override
                                            public String getKind() {
                                                return "";
                                            }

                                            @Override
                                            public StaticPart getStaticPart() {
                                                return null;
                                            }
                                        });
                                    }
                                }
                            }
                         //执行业务方法
                         try {
                            Object ret=method.invoke(bean,args);
                            //返回拦截
                            return ret;
                         }catch (Throwable e){
                              //异常拦截
                             throw e;
                         }finally {
                             //后置拦截
                         }
                        });
                //5替换原有的业务
                singletonObjects.put(id,proxyBeanMap);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        AopApplicationContext cxt=new AopApplicationContext(AopConfig.class);

        Object orderMapper = (IOrderMapper) cxt.getBean("orderMapper");
//        orderMapper.commit("abc");
    }
}
@Data
class AspectInto{
    String pointcut;//切点
    Method adviceMethod;//增强方法
    Object aspectBean;//切面对象

    //替换切点表达式
    void replacePoint() throws NoSuchMethodException {
        if(pointcut.matches("(\\w+)\\(\\)")){
            String pointcutMethod=pointcut.replaceFirst("(\\w+)\\(\\)","$1");
            Method method=aspectBean.getClass().getDeclaredMethod(pointcutMethod);
            Pointcut pointcutAno=method.getAnnotation(Pointcut.class);
            pointcut=pointcutAno.value();

        }
    }
}